/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.panomedic.core;

import com.panomedic.Constants;
import com.panomedic.utils.Utils;
import com.panomedic.colors.ColorConstants;
import com.panomedic.core.Intersection.isPxOpaqueEnum;
import com.panomedic.log4j.LogCategories;
import com.panomedic.log4j.Logger;
import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.imageio.ImageIO;
import org.apache.log4j.PropertyConfigurator;

/**
 *
 * @author Yare
 */
public class BoundarySeeker {

    static Logger logger = new Logger();
    protected BufferedImage bi = null;
    /** from Intersection - transparencyBitMap */
    isPxOpaqueEnum[][] transparencyBitmap;
    /** input - list of boundary ends */
    ArrayList<Point> boundaryEnds = null;
    protected static int maxX = 0;
    protected static int maxY = 0;
    /** result path */
    protected Set<Point> resultPath = new HashSet<Point>();
    /** for the needs of Dijkstra searching algorithm */
    protected SortedSet<Vertex> reached = new TreeSet<Vertex>();
    /** offset of bitmap - needed to subtract from the boundary points */
    private Point offset;

    public enum vState {

        UNREACHED, REACHED, FINAL
    }
    /** Vertex network */
    protected Vertex[][] raster = null;

    protected class Vertex implements Comparable {

        vState state = vState.UNREACHED;
        double left = -1;
        double top = -1;
        double distance = -1;
        int count = 0;
        int x = -1;
        int y = -1;
        Vertex previous = null;

        public Vertex(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int compareTo(Object o) {
            if (this.distance == ((Vertex) o).distance) {
                if (this.count > ((Vertex) o).count) {
                    return 1;
                } else if (this.count < ((Vertex) o).count) {
                    return -1;
                } else {
                    return 0;
                }
            } else if ((this.distance) > ((Vertex) o).distance) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    /** temporary constructor */
    private BoundarySeeker(BufferedImage bi) {
        this.bi = bi;
        transparencyBitmap = new isPxOpaqueEnum[bi.getWidth()][bi.getHeight()];
        for (int i = 0; i < transparencyBitmap.length; i++) {
            for (int j = 0; j < transparencyBitmap[i].length; j++) {
                transparencyBitmap[i][j] = isPxOpaqueEnum.ALL;
            }
        }
        offset = new Point(0,0);
               
    }

    public BoundarySeeker(BufferedImage bi,
            Intersection.isPxOpaqueEnum[][] transparencyBitmap,
            ArrayList<Point> boundaryEnds,
            Point offset) {
        this.bi = bi;
        this.boundaryEnds = boundaryEnds;
        this.transparencyBitmap = transparencyBitmap;
        this.offset = offset;
    }

    protected void initialize() {
        Raster r = bi.getRaster();
        maxX = r.getWidth();
        maxY = r.getHeight();

        logger.debug(LogCategories.PROCESSING, "maxX = " + maxX + ", maxY = " + maxY);

        raster = new Vertex[maxX][maxY];

        for (int x = 0; x < r.getWidth(); x++) {
            for (int y = 0; y < r.getHeight(); y++) {
                raster[x][y] = new Vertex(x, y);

                if (x != 0) {

                    int[] px1 = null;
                    px1 = r.getPixel(x - 1, y, px1);
                    int[] px2 = null;
                    px2 = r.getPixel(x, y, px2);

                    double value = Math.abs(Utils.getPixelValue(px1, ColorConstants.LCh) - Utils.getPixelValue(px2, ColorConstants.LCh));

                    raster[x][y].left = value;
                }
                if (y != 0) {
                    int[] px1 = null;
                    px1 = r.getPixel(x, y, px1);
                    double v1 = Utils.getPixelValue(px1, ColorConstants.LCh);
                    int[] px2 = null;
                    px2 = r.getPixel(x, y - 1, px2);
                    double v2 = Utils.getPixelValue(px2, ColorConstants.LCh);

                    double value = Math.abs(v1 - v2);

                    raster[x][y].top = value;
                }

            }

        }

    }

    public boolean dijkstra(Point start, Point finish) {
        
        logger.debug(LogCategories.PREPROCESS, "Seeking the boundary...");

        if (raster.length < start.x 
                || raster.length < finish.x 
                || raster[start.x].length < start.y 
                || raster[start.y].length < finish.y) {
            return false;
        }

        // 1
        Vertex v0 = raster[start.x][start.y];
        v0.distance = 0;
        reached.add(v0);

        int counter = 0;
        // 4
        while (!reached.isEmpty()) {
            // 5
            Vertex first = reached.first();

//            if (counter%1000 == 0)
//                logger.debug(first.x+","+first.y);
            // 6
            first.state = vState.FINAL;
            reached.remove(reached.first());

            int x = first.x;
            int y = first.y;

            if (x > 0 // Vertex stands inside the intersection
                    && raster[x - 1][y].state != vState.FINAL // Vertex is not FINAL
                    && transparencyBitmap[x - 1][y] == isPxOpaqueEnum.ALL) {         // pixel is opaque in all intersected photos
                Vertex w = raster[x - 1][y];
                double newDist = getDistance(first.distance, first.left);
                int newCount = first.count + 1;
                if (w.state == vState.UNREACHED || w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                    w.state = vState.REACHED;
                    if (w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                        reached.remove(w);
                    }
                    w.count = newCount;
                    w.distance = newDist;
                    w.previous = first;
                    reached.add(w);
                }
            }

            if (x < maxX - 1 && raster[x + 1][y].state != vState.FINAL && transparencyBitmap[x + 1][y] == isPxOpaqueEnum.ALL) {
                Vertex w = raster[x + 1][y];
                double newDist = getDistance(first.distance, w.left);
                int newCount = first.count + 1;
                if (w.state == vState.UNREACHED || w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                    w.state = vState.REACHED;
                    if (w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                        reached.remove(w);
                    }
                    w.count = newCount;
                    w.distance = newDist;
                    w.previous = first;
                    reached.add(w);
                }
            }
            if (y > 0 && raster[x][y - 1].state != vState.FINAL && transparencyBitmap[x][y - 1] == isPxOpaqueEnum.ALL) {
                Vertex w = raster[x][y - 1];
                double newDist = getDistance(first.distance, first.top);
                int newCount = first.count + 1;
                if (w.state == vState.UNREACHED || w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                    w.state = vState.REACHED;
                    if (w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                        reached.remove(w);
                    }
                    w.count = newCount;
                    w.distance = newDist;
                    w.previous = first;
                    reached.add(w);
                }
            }
            if (y < maxY - 1 && raster[x][y + 1].state != vState.FINAL && transparencyBitmap[x][y + 1] == isPxOpaqueEnum.ALL) {
                Vertex w = raster[x][y + 1];
                double newDist = getDistance(first.distance, w.top);
                int newCount = first.count + 1;
                if (w.state == vState.UNREACHED || w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                    w.state = vState.REACHED;
                    if (w.distance > newDist || (w.distance == newDist && w.count > newCount)) {
                        reached.remove(w);
                    }
                    w.count = newCount;
                    w.distance = newDist;
                    w.previous = first;
                    reached.add(w);
                }
            }
        }

        Vertex v = raster[finish.x][finish.y];
        while (v.previous != null) {
//            logger.debug(v.x+","+v.y);
            ((HashSet) resultPath).add(new Point(v.x + offset.x, v.y + offset.y));
            v = v.previous;
        }
        ((HashSet) resultPath).add(new Point(v.x + offset.x, v.y + offset.y));

        return true;
    }

    protected double getDistance(double v1, double v2) {
//        return Math.min(v1, v2);
        return v1 + v2;
    }

    public void printOutPath(String file) {
        try {
            PrintStream out = new PrintStream(new FileOutputStream(file));
//            out.println("Path to point " + finish.x + "," + finish.y + ": ");
            Iterator li = resultPath.iterator();
            while (li.hasNext()) {
                Point p = (Point) li.next();
                out.println(p.x + "," + p.y);
            }
            out.close();
        } catch (IOException ex) {
            logger.debug(BoundarySeeker.class.getName());
        }

    }

    public void drawPath(Color color) {
        WritableRaster r = bi.getRaster();
        Iterator li = resultPath.iterator();
        float[] px = new float[3];
        px = color.getRGBColorComponents(px);
//        logger.debug(LogCategories.PROCESSING, Arrays.toString(px));
        int[] px2 = new int[4];
        px2[0] = (int) (255 * px[0]);
        px2[1] = (int) (255 * px[1]);
        px2[2] = (int) (255 * px[2]);
        px2[3] = 255;
        logger.debug(LogCategories.PROCESSING, "Debug boundary color: " + Arrays.toString(px2));

        while (li.hasNext()) {
            Point p = (Point) li.next();

            try {
                r.setPixel(p.x - offset.x, p.y - offset.y, px2);
            } catch (ArrayIndexOutOfBoundsException ex) {
                logger.error(LogCategories.PROCESSING, "setPixel - arrayIndexOut: p.x = " + p.x + ", p.y = " + p.y + "\t Ex:" + ex.toString());
            }
        }
        bi.setData(r);
    }

    public Set<Point> getResultPath() {
        return resultPath;
    }

    public HashSet<Point> seek(ArrayList<Point> boundaryEnds) {
        for (int i = 0; i + 1 < boundaryEnds.size(); i += 2) {
            Point pStart = new Point(boundaryEnds.get(i).x - offset.x, boundaryEnds.get(i).y - offset.y);
            Point pEnd = new Point(boundaryEnds.get(i + 1).x - offset.x, boundaryEnds.get(i + 1).y - offset.y);
            this.dijkstra(pStart, pEnd);
        }
//        test();
        return (HashSet<Point>) resultPath;
    }

    public void test() {

//        Point pStart = new Point(boundaryEnds.get(0).x - offset.x, boundaryEnds.get(0).y - offset.y);
//        Point pEnd = new Point(boundaryEnds.get(1).x - offset.x, boundaryEnds.get(1).y - offset.y);
//        this.dijkstra(pStart, pEnd);

        //  DEBUG - boundary
        String fileNameCore = "result_bs_test";
        int counter = 1;

        String fileName = fileNameCore + "_path" + counter + ".log";
        File file = new File(fileName);
        while (file.exists()) {
            counter++;
            fileName = fileNameCore + "_path" + counter + ".log";
            file = new File(fileName);
        }

        this.printOutPath(fileNameCore + "_path" + counter + ".log");
        drawPath(Color.BLUE);
        try {
            ImageIO.write(bi, "png", new File(fileNameCore + "_result" + counter + ".png"));
        } catch (IOException ex) {
            logger.debug(ex.getMessage());
        }
    // ~DEBUG

    }

    public static void main(String[] args) {

        PropertyConfigurator.configure(Constants.LOG_PROPERTIES_FILE);

        // boundary in whole image
        BufferedImage bi = null;
        String fileName = "s3";
        logger.debug(LogCategories.PROCESSING, "Seeking the boundary...");
        try {
            bi = ImageIO.read(new File("D:\\School\\Rocnikovy projekt\\testimages\\boundary\\" + fileName + ".png"));
        } catch (IOException ex) {
            
            logger.debug(ex.getMessage());
        }

        BoundarySeeker bs = new BoundarySeeker(bi);
        Point p1 = new Point(0, 0);
        Point p2 = new Point(bi.getWidth() - 1, bi.getHeight() - 1);
        int counter = 7;
        bs.initialize();
        bs.dijkstra(p1, p2);
        bs.printOutPath(fileName + "_path" + counter + ".log");
        bs.drawPath(Color.WHITE);
        try {

            ImageIO.write(bs.bi, "png", new File(fileName + "_result" + counter + ".png"));
        } catch (IOException ex) {
            logger.debug(ex.getMessage());
        }



    }
}
